/*
 * Copyright (c) zhg2yqq.com Corp.
 * All Rights Reserved.
 */
package com.zhg2yqq.wheels.mvc.util;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.zhg2yqq.wheels.mvc.annotation.FieldEncrypt;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;

/**
 * FieldEncrypt注解加密解密工具
 *
 * @author zhg2yqq, 2022年12月5日
 * @version zhg2yqq v1.0
 */
public class FieldEncryptUtil {
    private FieldEncryptUtil() {
    }

    @SuppressWarnings("unchecked")
    public static void decryptField(String encryptKey, Object obj) {
        if (obj == null || ObjectUtil.isBasicType(obj)) {
            return;
        }
        if (obj instanceof Collection) {
            Collection<Object> objs = (Collection<Object>) obj;
            List<Object> encryptEntities = objs.stream().filter(Objects::nonNull).distinct()
                    .filter(o -> !ObjectUtil.isBasicType(o)).collect(Collectors.toList());
            if (encryptEntities.isEmpty()) {
                return;
            }
            Set<Field> encryptFields = getNeedEncryptFields(encryptEntities.get(0).getClass());
            if (encryptFields.isEmpty()) {
                return;
            }
            for (Object each : encryptEntities) {
                decryptFields(encryptKey, encryptFields, each);
            }
        } else if (obj instanceof Map) {
            Map<String, Object> objs = (Map<String, Object>) obj;
            List<Object> encryptEntities = objs.entrySet().stream().map(Entry::getValue)
                    .filter(Objects::nonNull).distinct().filter(o -> !ObjectUtil.isBasicType(o))
                    .collect(Collectors.toList());
            if (encryptEntities.isEmpty()) {
                return;
            }
            for (Object each : encryptEntities) {
                Set<Field> encryptFields = getNeedEncryptFields(each.getClass());
                decryptFields(encryptKey, encryptFields, each);
            }
        } else {
            Set<Field> encryptFields = getNeedEncryptFields(obj.getClass());
            decryptFields(encryptKey, encryptFields, obj);
        }
    }

    private static void decryptFields(String encryptKey, Collection<Field> encryptFields,
                                      Object result) {
        for (Field encryptField : encryptFields) {
            Object value = ReflectUtil.getFieldValue(result, encryptField);
            if (value instanceof String) {
                FieldEncrypt annotation = encryptField.getAnnotation(FieldEncrypt.class);
                String key = CharSequenceUtil.isEmpty(annotation.encryptorPassword()) ? encryptKey
                        : annotation.encryptorPassword();
                Object decryptValue = decrypt(key, value);
                ReflectUtil.setFieldValue(result, encryptField, decryptValue);
            }
        }
    }

    public static Object decrypt(String encryptKey, Object value) {
        if (value instanceof String && CharSequenceUtil.isNotEmpty((String) value)) {
            SM4 sm4 = CharSequenceUtil.isEmpty(encryptKey) ? SmUtil.sm4()
                    : SmUtil.sm4(encryptKey.getBytes());
            return sm4.decryptStr((String) value, CharsetUtil.CHARSET_UTF_8);
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    public static void encryptField(String encryptKey, Object obj) {
        if (obj == null || ObjectUtil.isBasicType(obj)) {
            return;
        }
        if (obj instanceof Collection) {
            Collection<Object> objs = (Collection<Object>) obj;
            List<Object> encryptEntities = objs.stream().filter(Objects::nonNull).distinct()
                    .filter(o -> !ObjectUtil.isBasicType(o)).collect(Collectors.toList());
            if (encryptEntities.isEmpty()) {
                return;
            }
            Set<Field> encryptFields = getNeedEncryptFields(encryptEntities.get(0).getClass());
            if (encryptFields.isEmpty()) {
                return;
            }
            for (Object each : encryptEntities) {
                encryptFields(encryptKey, encryptFields, each);
            }
        } else if (obj instanceof Map) {
            Map<String, Object> objs = (Map<String, Object>) obj;
            List<Object> encryptEntities = objs.entrySet().stream().map(Entry::getValue)
                    .filter(Objects::nonNull).distinct().filter(o -> !ObjectUtil.isBasicType(o))
                    .collect(Collectors.toList());
            if (encryptEntities.isEmpty()) {
                return;
            }
            for (Object each : encryptEntities) {
                Set<Field> encryptFields = getNeedEncryptFields(each.getClass());
                encryptFields(encryptKey, encryptFields, each);
            }
        } else {
            Set<Field> encryptFields = getNeedEncryptFields(obj.getClass());
            encryptFields(encryptKey, encryptFields, obj);
        }
    }

    private static void encryptFields(String encryptKey, Collection<Field> encryptFields,
                                      Object result) {
        for (Field encryptField : encryptFields) {
            Object value = ReflectUtil.getFieldValue(result, encryptField);
            if (value instanceof String) {
                FieldEncrypt annotation = encryptField.getAnnotation(FieldEncrypt.class);
                String key = CharSequenceUtil.isEmpty(annotation.encryptorPassword()) ? encryptKey
                        : annotation.encryptorPassword();
                Object encryptValue = encrypt(key, value);
                ReflectUtil.setFieldValue(result, encryptField, encryptValue);
            }
        }
    }

    public static Object encrypt(String encryptKey, Object value) {
        if (value instanceof String && CharSequenceUtil.isNotEmpty((String) value)) {
            SM4 sm4 = CharSequenceUtil.isEmpty(encryptKey) ? SmUtil.sm4()
                    : SmUtil.sm4(encryptKey.getBytes());
            return sm4.encryptHex((String) value, CharsetUtil.CHARSET_UTF_8);
        }
        return value;
    }

    private static Set<Field> getNeedEncryptFields(Class<?> objClass) {
        Field[] fields = ReflectUtil.getFields(objClass,
                f -> f.isAnnotationPresent(FieldEncrypt.class));
        if (fields == null) {
            return Collections.emptySet();
        }
        return new HashSet<>(Arrays.asList(fields));
    }
}
